/*===============================================================================================
 *  EECS 395: nu-Portal                                                                         *
 *  Programmed by Jeremy Lee                                                                    *
 *  A simple 3D game with a warping mechanic                                                    *
 *  All Models found from turbosquid.com for free                                               *
 *  Camera system based off of the tutorial at                                                  *
 *  http://www.3dgameprogramming.net/2007/07/31/getting-started-with-xna-first-person-camera/   *
 *  ============================================================================================*/

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace nu_portal
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Camera player;
        Obj3D box1, box2;
        Obj3D portal1a, portal1b, portal2a, portal2b;
        Obj3D[] boxCollection, cornerPillar;
        SpriteFont font;
        List<Obj3D> lst = new List<Obj3D>();
        double portalTime1, portalTime2, jumpTime;
        bool win = false;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            IsFixedTimeStep = false;
            graphics.PreferredBackBufferWidth = 800;
            graphics.PreferredBackBufferHeight = 600;
            graphics.ApplyChanges();
        }

        protected override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            font = Content.Load<SpriteFont>("fonts\\Courier New");
            player = new Camera(Content.Load<Model>("models\\Crate_Fragile"));
            player.size = new Vector3(100);
            player.offset = new Vector3(0, 50, 0);
            box1 = new Obj3D(Content.Load<Model>("models\\Crate_Fragile"));
            box1.pos = new Vector3(0, 0, -500);
            box1.offset = new Vector3(0, 50, 0);
            box1.size = new Vector3(100);
            box2 = new Obj3D(Content.Load<Model>("models\\BOX"));
            box2.pos = new Vector3( 500, 1200, -500);
            //box2.pos = new Vector3(250, 0, -250);
            box2.offset = new Vector3(0, 50, 0);
            box2.size = new Vector3(100);
            portal1a = new Obj3D(Content.Load<Model>("models\\Ball Thing"));
            portal1a.pos = new Vector3(400, 800, 800);
            portal1a.offset = new Vector3(0, 25, 0);
            portal1a.size = new Vector3(50);
            portal1b = new Obj3D(Content.Load<Model>("models\\Ball Thing"));
            portal1b.pos = new Vector3(0, 1050, -300);
            portal1b.offset = new Vector3(0, 25, 0);
            portal1b.size = new Vector3(50);
            portal2a = new Obj3D(Content.Load<Model>("models\\Ball Thing"));
            portal2a.pos = new Vector3(-200, 75, -500);
            portal2a.offset = new Vector3(0, 25, 0);
            portal2a.size = new Vector3(50);
            portal2b = new Obj3D(Content.Load<Model>("models\\Ball Thing"));
            portal2b.pos = new Vector3(900, 75, 750);
            portal2b.offset = new Vector3(0, 25, 0);
            portal2b.size = new Vector3(50);

            boxCollection = new Obj3D[15];
            for (int i = 0; i < 10; i++)
            {
                boxCollection[i] = new Obj3D(Content.Load<Model>("models\\Crate_Fragile"));
                if( i<5 )
                    boxCollection[i].pos = new Vector3(0 + 100 * i, 100 * i, 1000);
                else
                    boxCollection[i].pos = new Vector3(-500 + 100 * i, 200 + 100 * i, -500);
                boxCollection[i].offset = new Vector3(0, 50, 0);
                boxCollection[i].size = new Vector3(100);
            }
            for (int i = 10; i < 15; i++)
            {
                boxCollection[i] = new Obj3D(Content.Load<Model>("models\\Crate_Fragile"));
                boxCollection[i].pos = new Vector3(-500 + 100 * i, -225 + 75*i, 1250 - 25*i);
                boxCollection[i].offset = new Vector3(0,50,0);
                boxCollection[i].size = new Vector3(100);
            }

            cornerPillar = new Obj3D[8];
            for (int i = 0; i < 8; i++)
            {
                cornerPillar[i] = new Obj3D(Content.Load<Model>("models\\roman_pedestal"));
                cornerPillar[i].offset = new Vector3(0, 0, 0);
                cornerPillar[i].size = new Vector3(100);
            }
            cornerPillar[0].pos = new Vector3(-4000, -150, -4000);
            cornerPillar[1].pos = new Vector3(4000, -150, -4000);
            cornerPillar[2].pos = new Vector3(4000, -150, 4000);
            cornerPillar[3].pos = new Vector3(-4000, -150, 4000);
            cornerPillar[4].pos = new Vector3(-4000, -150, 0);
            cornerPillar[5].pos = new Vector3(4000, -150, 0);
            cornerPillar[6].pos = new Vector3(0, -150, -4000);
            cornerPillar[7].pos = new Vector3(0, -150, 4000);

            lst.Add(box1);
            lst.Add(portal1a);
            lst.Add(portal1b);
            lst.Add(portal2a);
            lst.Add(portal2b);
            lst.Add(box2);
            foreach (Obj3D o in boxCollection)
                lst.Add(o);
            foreach (Obj3D o in cornerPillar)
                lst.Add(o);
            portalTime1 = 0;
            portalTime2 = 0;
            jumpTime = 0;
        }

        protected override void UnloadContent() { }

        protected override void Update(GameTime gameTime)
        {
            float elapsedTime = (float)gameTime.ElapsedGameTime.TotalMilliseconds;
            KeyboardState ks = Keyboard.GetState();
            MouseState ms = Mouse.GetState();

            if (ks.IsKeyDown(Keys.Escape))
                this.Exit();
            if (ks.IsKeyDown(Keys.R))
            {
                player.pos = Vector3.Zero;
                player.inAir = false;
                player.vel = Vector3.Zero;
                player.angle = Vector3.Zero;
                win = false;
            }
            if (win)
                return;

            int centerX = this.Window.ClientBounds.Width / 2;
            int centerY = this.Window.ClientBounds.Height / 2;
            Mouse.SetPosition(centerX, centerY);

            player.angle.X += MathHelper.ToRadians((ms.Y - centerY) * 0.05f); // pitch
            if (player.angle.X >= MathHelper.PiOver2)
                player.angle.X = MathHelper.PiOver2;
            if (player.angle.X <= -MathHelper.PiOver2)
                player.angle.X = -MathHelper.PiOver2;
            player.angle.Y += MathHelper.ToRadians((ms.X - centerX) * 0.05f); // yaw

            Vector3 forward = Vector3.Normalize(
                                new Vector3((float)Math.Sin(-player.angle.Y), 
                                            (float)Math.Sin(player.angle.X), 
                                            (float)Math.Cos(-player.angle.Y)));
            forward.Y = 0;  // Keep the player moving on the same XZ-plane
            Vector3 left = Vector3.Normalize(
                                   new Vector3((float)Math.Cos(player.angle.Y), 0f, (float)Math.Sin(player.angle.Y)));
            left.Y = 0; // Ditto

            Vector3 prevPos = player.pos;

            float airFact = 0.007f;
            if (player.inAir)
                airFact = 0.0008f;

            if (ks.IsKeyDown(Keys.W))
            {
                player.pos -= forward * 0.05f * elapsedTime;
                player.vel -= forward * airFact * elapsedTime;
            }

            if (ks.IsKeyDown(Keys.S))
            {
                player.pos += forward * 0.05f * elapsedTime;
                player.vel += forward * airFact * elapsedTime;
            }

            if (ks.IsKeyDown(Keys.D))
            {
                player.pos += left * 0.05f * elapsedTime;
                player.vel += left * airFact * elapsedTime;
            }

            if (ks.IsKeyDown(Keys.A))
            {
                player.pos -= left * 0.05f * elapsedTime;
                player.vel -= left * airFact * elapsedTime;
            }

            bool collide = false;

            if (player.collides(box1))
            {
                checkCollide(box1, prevPos);
                collide = true;
            }
            for (int i = 0; i < 15; i++)
                if (player.collides(boxCollection[i]))
                {
                    checkCollide(boxCollection[i], prevPos);
                    collide = true;
                    if (player.collidePlane(boxCollection[i]).Y < 0)
                        player.inAir = false;
                }

            if (!collide && player.pos.Y > 0)
                player.inAir = true;

            if (ks.IsKeyDown(Keys.Space))
            {
                if (!player.inAir && jumpTime < 0)
                {
                    player.vel.Y += 1.4f;
                    player.inAir = true;
                    jumpTime += 400;
                }
            }

            if (jumpTime >= 0)
                jumpTime -= elapsedTime;

            if (player.collides(box2))
                win = true;

            if (player.collides(portal1a) && portalTime1 < 0)
            {
                Vector3 tmpOffset = player.pos - portal1a.pos;
                player.pos = portal1b.pos - tmpOffset;
                player.inAir = true;
                portalTime1 += 3000;
            }

            if (player.collides(portal1b) && portalTime1 < 0)
            {
                Vector3 tmpOffset = player.pos - portal1b.pos;
                player.pos = portal1a.pos - tmpOffset;
                player.inAir = true;
                portalTime1 += 3000;
            }

            if (player.collides(portal2a) && portalTime2 < 0)
            {
                Vector3 tmpOffset = player.pos - portal2a.pos;
                player.pos = portal2b.pos - tmpOffset;
                player.inAir = true;
                player.vel.Y = -player.vel.Y;
                portalTime2 += 300;
            }

            if (player.collides(portal2b) && portalTime2 < 0)
            {
                Vector3 tmpOffset = player.pos - portal2b.pos;
                player.pos = portal2a.pos - tmpOffset;
                player.inAir = true;
                player.vel.Y = -player.vel.Y;
                portalTime2 += 300;
            }

            if (portalTime1 >= 0)
                portalTime1 -= elapsedTime;

            if (portalTime2 >= 0)
                portalTime2 -= elapsedTime;
            if (player.inAir)
            {
                player.vel.X *= 0.95f;
                player.vel.Z *= 0.95f;
            }
            else
            {
                player.vel.X *= 0.8f;
                player.vel.Z *= 0.8f;
            }
            if (Math.Abs(player.vel.X) + Math.Abs(player.vel.Z) <= 0.01f)
            {
                player.vel.X = 0;
                player.vel.Z = 0;
            }
            if (player.vel.Y > 100)
                player.vel.Y = 100;
            if (player.vel.Y < -100)
                player.vel.Y = -100;

            player.update(elapsedTime);

            if (player.pos.X >= 2000)
            {
                player.pos.X = 2000;
                player.vel.X = 0;
            }
            if (player.pos.X <= -2000)
            {
                player.pos.X = -2000;
                player.vel.X = 0;
            }
            if (player.pos.Z >= 2000)
            {
                player.pos.Z = 2000;
                player.vel.Z = 0;
            }
            if (player.pos.Z <= -2000)
            {
                player.pos.Z = -2000;
                player.vel.Z = 0;
            }

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.LightGray);

            Matrix[] transforms = new Matrix[player.model.Bones.Count];

            for (int i = 0; i < lst.Count; i++)
            {
                float maxDist = (lst[i].pos - player.pos).LengthSquared();
                int maxIndex = 0;
                for (int j = i; j < lst.Count; j++)
                {
                    float dist = (lst[j].pos - player.pos).LengthSquared();
                    if (dist >= maxDist)
                    {
                        maxDist = dist;
                        maxIndex = j;
                    }
                }
                Obj3D swap = lst[i];
                lst[i] = lst[maxIndex];
                lst[maxIndex] = swap;
            }

            spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, null, DepthStencilState.DepthRead, null);
                for (int i = 0; i < lst.Count; i++)
                    lst[i].draw(player.view, player.projection);
                //*
                spriteBatch.DrawString(font, "Get to the present to win", Vector2.Zero, Color.Green);
                spriteBatch.DrawString(font, "The spherical doohickeys are portals", 
                                       new Vector2(0, 12), Color.Green);
                spriteBatch.DrawString(font, "WASD to move forwards/left/back/right", 
                                       new Vector2(0, 24), Color.Green);
                spriteBatch.DrawString(font, "Space to Jump", 
                                       new Vector2(0, 36), Color.Green);
                spriteBatch.DrawString(font, "R to restart the level",
                                       new Vector2(0, 48), Color.Green);
                spriteBatch.DrawString(font, "Esc to exit the game",
                                       new Vector2(0, 60), Color.Green);
                spriteBatch.DrawString(font, "Use the mouse to look around",
                                       new Vector2(0, 72), Color.Green);
                //*/
                /*
                spriteBatch.DrawString(font, "Test", Vector2.Zero, Color.Green);
                spriteBatch.DrawString(font, "Player location is currently X: " + player.pos.X + " Y: " + 
                                             player.pos.Y + " Z: " + player.pos.Z, 
                                       new Vector2(0, 12), Color.Green);
                spriteBatch.DrawString(font, "Player Bounding Box is currently " + player.makeBox().ToString(), 
                                       new Vector2(0, 24), Color.Green);
                spriteBatch.DrawString(font, "Player velocity is currently " + player.vel.ToString(),
                                       new Vector2(0, 36), Color.Green);
                spriteBatch.DrawString(font, "Player inAir status: " + player.inAir.ToString(),
                                       new Vector2(0, 48), Color.Green);
                spriteBatch.DrawString(font, "portalTimer is currently: " + portalTime1, new Vector2(0, 60), Color.Green);
                spriteBatch.DrawString(font, "jumpTimer is currently: " + jumpTime, new Vector2(0, 72), Color.Green);
                spriteBatch.DrawString(font, "Box location is currently X: " + box1.pos.X + " Y: " + box1.pos.Y + " Z: " + box1.pos.Z,
                                       new Vector2(0, 80), Color.Green);
                spriteBatch.DrawString(font, "Box Bounding Box is currently " + box1.makeBox().ToString(),
                                       new Vector2(0, 92), Color.Green);

                for( int i=0; i<lst.Count; i++ )
                {
                    if (player.collides(lst[i]))
                    {
                        spriteBatch.DrawString(font, "Player is colliding with object.", new Vector2(0, 120 + 24*i), Color.Red);
                        spriteBatch.DrawString(font, "CollideAxis is " + player.collidePlane(lst[i]).ToString(), new Vector2(0, 132 + 24 * i), Color.Red);
                    }
                }
                //*/
                if (win)
                {
                    spriteBatch.DrawString(font, "YOU WIN!!!", new Vector2(250, 200), Color.Lime, 0, Vector2.Zero, 5, SpriteEffects.None, 0);
                    spriteBatch.DrawString(font, "Press R to restart, ESC to quit", new Vector2( 290, 260), Color.Lime);
                }
            spriteBatch.End();

            base.Draw(gameTime);
        }

        private void checkCollide(Obj3D obj, Vector3 prevPos)
        {
            Vector3 tmpVec = player.collidePlane(obj);
            if (tmpVec.X != 0)
            {
                player.pos.X = prevPos.X;
                player.vel.X = 0;
            }
            if (tmpVec.Y != 0)
            {
                player.pos.Y = prevPos.Y;
                if (tmpVec.Y > 0)
                {
                    player.vel.Y = 0;
                    player.inAir = true;
                }
                else
                {
                    player.vel.Y = 0;
                    player.inAir = false;
                }
            }
            if (tmpVec.Z != 0)
            {
                player.pos.Z = prevPos.Z;
                player.vel.Z = 0;
            }
            if (tmpVec.X == 0 && tmpVec.Y == 0 && tmpVec.Z == 0)
            {
                player.vel.Y = 0;
                player.inAir = false;
            }
        }
    }
}
